//
//  This copyrighted © code is written for and is part of the book
//  Smartphone-Based Real-Time Digital Signal Processing
//
#include <jni.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <android/log.h>
#include "Timer.h"
#include "Transforms.h"

typedef struct Variables {
	Timer* timer;
	Transform* fft;
	Transform* filter;
	float* inputBuffer;
	int stepSize;
	int windowSize;
	int overlap;
	float fftFactor;
} Variables;

float filter_coeff[]={
                    //**************************************************
                    //put the coefficient computed by MATLAB code here.
                      };

int nn=0;

jlong
initialize(JNIEnv* env, jobject thiz, jint stepsize, jint windowsize, int window, int fftPoints)
{
	Variables* inParam = (Variables*) malloc(sizeof(Variables));
	inParam->timer = newTimer();
	inParam->fft = newTransform(windowsize, window, fftPoints);
    //TO do:  initialize another forward transform here for MATLAB coefficients
	if(stepsize > 0) {
		inParam->overlap = windowsize - stepsize;
		inParam->stepSize = stepsize;
	} else {
		inParam->overlap = 0;
		inParam->stepSize = windowsize;
	}
	inParam->windowSize = windowsize;
	inParam->inputBuffer = (float*)calloc(windowsize,sizeof(float));
	inParam->fftFactor = 1.0/(fftPoints);
	return (jlong)inParam;
}

static void
compute(JNIEnv *env, jobject thiz,  jlong memoryPointer, jshortArray input)
{
	Variables* inParam = (Variables*) memoryPointer;
	startTimer(inParam->timer);

	short *_in = (*env)->GetShortArrayElements(env, input, NULL);

	int i;
	for(i=0; i<inParam->overlap; i++)
	{
		inParam->inputBuffer[i] = inParam->inputBuffer[inParam->stepSize + i];
	}

	for(i=0; i<inParam->stepSize; i++)
	{
		inParam->inputBuffer[inParam->overlap + i] = _in[i]/32768.0f;
	}

	(*env)->ReleaseShortArrayElements(env, input, _in, 0);

	ForwardFFT(inParam->fft, inParam->inputBuffer);
    // ************************************************
    //use Forward transform using MATLAB coefficients



    // ************************************************
    // Use __android_log_print command to print and save parameters from FFT



    // ************************************************
    // Write required codes for InverseFFT filter here



    // ************************************************
    // Use __android_log_print command to print and save parameters from InverseFFT


	stopTimer(inParam->timer);
}

static jfloatArray
copyArray(JNIEnv* env, float* array, int length)
{
	jfloatArray output = (*env)->NewFloatArray(env, length);
	float *_output = (*env)->GetFloatArrayElements(env, output, NULL);

	int i;
	for (i=0; i<length;i++)
	{
		_output[i] = array[i];
	}

	(*env)->ReleaseFloatArrayElements(env, output, _output, 0);

	return output;
}

static jfloatArray
getDebug(JNIEnv* env, jobject thiz, jlong memoryPointer, jint debugSelect)
{
	Variables* inParam = (Variables*) memoryPointer;

	jfloatArray debugOutput = NULL;

	if(debugSelect == -5){

		//Spectrum Output Case
		debugOutput = (*env)->NewFloatArray(env, (inParam->fft->points/2+1));
		float *_debugOutput = (*env)->GetFloatArrayElements(env, debugOutput, NULL);

		int i;
		for (i=0; i<(inParam->fft->points/2+1);i++) {
			_debugOutput[i] = sqrt(inParam->fft->power[i]*inParam->fftFactor);
		}

		(*env)->ReleaseFloatArrayElements(env, debugOutput, _debugOutput, 0);

	} else if(debugSelect == 0){

		//Test Case 0 - inputBuffer contents
		debugOutput = copyArray(env, inParam->inputBuffer, inParam->windowSize);

	} else if(debugSelect == 1) {

		//Test Case 1 - input frame
		debugOutput = copyArray(env, &(inParam->inputBuffer[inParam->overlap]), inParam->stepSize);

	} else if (debugSelect == 2) {

		//Test Case 3 - FFT Power Spectrum
		debugOutput = copyArray(env, inParam->fft->power, (inParam->fft->points/2+1));

	} else if (debugSelect == 3) {

		//Test Case 4 - FFT Real Portion
		debugOutput = copyArray(env, inParam->fft->real, (inParam->fft->points/2+1));

	} else if (debugSelect == 4) {

		//Test Case 5 - FFT Imaginary Portion
		debugOutput = copyArray(env, inParam->fft->imaginary, (inParam->fft->points/2+1));

	}

	return debugOutput;
}

float
getTime(JNIEnv* env, jobject thiz, jlong memoryPointer)
{
	Variables* inParam = (Variables*) memoryPointer;
	return getTimerMS(inParam->timer);
}

void
finish(JNIEnv* env, jobject thiz, jlong memoryPointer)
{
	Variables* inParam = (Variables*) memoryPointer;
	if(inParam != NULL){
		if(inParam->inputBuffer != NULL){
			free(inParam->inputBuffer);
			inParam->inputBuffer = NULL;
		}
		#ifdef LOGCAT_DEBUG
			tellTimeTimer(inParam->timer);
		#endif
		destroyTimer(&(inParam->timer));
		destroyTransform(&(inParam->fft));
		free(inParam);
		inParam = NULL;
	}
}

////////////////////////////////////////////////////////////////////////////////////////////
// JNI Setup
////////////////////////////////////////////////////////////////////////////////////////////

static JNINativeMethod nativeMethods[] =
	{//		Name							Signature			Pointer
			{"initialize",					"(IIII)J",			(void *)&initialize				},
			{"compute", 					"(J[S)V",			(void *)&compute				},
			{"getTime",						"(J)F",				(void *)&getTime				},
			{"getDebug",					"(JI)[F",			(void *)&getDebug				},
			{"finish",						"(J)V",				(void *)&finish					}

	};

jint
JNI_OnLoad(JavaVM* vm, void* reserved)
{
	JNIEnv* env;
	jint result;
	//get a hook to the environment
	result = (*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6);
	if (result == JNI_OK) {
		//find the java class to hook the native methods to
		jclass filters = (*env)->FindClass(env, "com/dsp/spectrogram/Spectrogram");
		if (filters != NULL) {
			result = (*env)->RegisterNatives(env, filters, nativeMethods, sizeof(nativeMethods)/sizeof(nativeMethods[0]));
			(*env)->DeleteLocalRef(env, filters);
			if(result == JNI_OK){
				return JNI_VERSION_1_6;
			} else {
				//something went wrong with the method registration
				return JNI_ERR;
			}
		} else {
			//class wasn't found
			return JNI_ERR;
		}
	} else {
		//could not get environment
		return JNI_ERR;
	}
}
